package core;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Random;

import core.functions.Problem;
import util.Parameters;

public class GeneticAlgorithm {
	
	public static Problem p = new Problem();
	public static double bestFitness = Double.MAX_VALUE;
	public static Solution bestSolution = null;
	public static int stagnated = 0;
	
	public static Solution[] listBestSolutions = new Solution[Parameters.numberIterations];
		
	public static void DoGenectiAlgorithm(int numberIterations, int num) {
		// reiniciar atributos de melhor solucao
		bestFitness = Double.MAX_VALUE;
		bestSolution = null;
		// criar solucoes iniciais
		Solution[] parents = GenerateInitialSolutions();
		double tempMutation = Parameters.mutationPercentage;
		
		for (int i = 0; i < numberIterations; i++) {
			//verificar burst de mutacao
			if(stagnated >= Parameters.mutationBurstCount){
				Parameters.mutationPercentage = Parameters.mutationBurstPercentage;
			}else{
				Parameters.mutationPercentage = tempMutation;
			}
			
			// fazer a prole - crossover
			Solution[] children = DoCrossOver(parents, i);
			
			// fazer mutacao
			DoMutation(children);
			
			// selecionar proxima geracao
			children = DoSelectionBestFitness(parents, children);
			//children = DoSelectionTournament(parents, children);
			// verificar melhor solu��o
			for (int j = 0; j < children.length; j++) {
				if(children[j].getFitness() < bestFitness){
					bestSolution = children[j];
					bestFitness = bestSolution.getFitness();
					stagnated = 0;
				}else{
					stagnated++;
				}
			}
			//System.out.println(i+"\t\t"+bestSolution.getFitness());
			
			if(listBestSolutions[i] == null){
				listBestSolutions[i] = new Solution();
			}
			listBestSolutions[i].setFitness(bestSolution.getFitness() + listBestSolutions[i].getFitness());
			listBestSolutions[i].setGeneration(bestSolution.getGeneration());
			listBestSolutions[i].setVector(bestSolution.getVector());
			
			//organizando para proxima iteracao
			parents = children;
		}
		
		System.out.println(num+"\t\t"+bestSolution.getFitness()+"\t\t"
				+bestSolution.printVector());
	}
	
	private static Solution[] GenerateInitialSolutions() {
		Solution[] parents = new Solution[Parameters.numberParents];
		Random r = new Random();
		double number = 0.0;
		
		for (int i = 0; i < parents.length; i++) {
			parents[i] = new Solution();
			parents[i].setGeneration(-1); //gera��o inicial = -1
			//gerar vetor
			for (int j = 0; j < parents[i].getVector().length; j++) {
				number = p.minRange + r.nextDouble() * (p.maxRange - p.minRange);
				parents[i].setVectorElement(j, number);
			}
			//gerar valor (fun��o)
			parents[i].setFitness(p.EvaluateRastringin(parents[i].getVector()));
			//System.out.println(i+"\t" + parents[i].getValue() + "\t\t" + parents[i].printVector());
		}
		
		return parents;
	}
	
	private static Solution[] DoCrossOver(Solution[] parents, int gen) {
		int tam = parents.length;
		int half = tam/2;
		int pivot = (int) parents[0].getVector().length / 2;
				
		Solution[] parents1 = new Solution[tam/2];
		Solution[] parents2 = new Solution[tam/2];
		Solution[] children = new Solution[Parameters.numberParents];
		
		//preenchendo arrays
		for (int i = 0, j = tam/2; i < tam/2; i++, j++) {
			parents1[i] = parents[i];
			parents2[i] = parents[j];
		}
		
		//fazer crossover - filho 1
		for (int i = 0; i < half; i++) {
			children[i] = new Solution();
			children[i].setGeneration(gen);
			children[i].setVector(mixedVector(parents1[i].getVector(), parents2[i].getVector(), pivot));
			children[i].setFitness(p.EvaluateRastringin(children[i].getVector()));
		}
		
		//fazer crossover - filho 2
		for (int i = half, j =0 ; i < tam; i++, j++) {
			children[i] = new Solution();
			children[i].setGeneration(gen);
			children[i].setVector(mixedVector(parents2[j].getVector(), parents1[j].getVector(), pivot));
			children[i].setFitness(p.EvaluateRastringin(children[i].getVector()));
		}
		
		//desordenar
		ArrayList<Solution> ret = new ArrayList<Solution>(Arrays.asList(children));
		Collections.shuffle(ret, new Random());
		
		ret.toArray(children);
		
		return children;
	}
	
	private static void DoMutation(Solution[] newGen) {
		Random r = new Random();
		for (int i = 0; i < newGen.length; i++) {
			for (int j = 0; j < newGen[i].getVector().length; j++) {
				double percentage = r.nextDouble();
				if(percentage <= Parameters.mutationPercentage){
					//ocorre muta��o
					double newValue = p.minRange + r.nextDouble() * (p.maxRange - p.minRange);
					newGen[i].setVectorElement(j, newValue);
				}
			}
		}
	}
	
	private static Solution[] DoSelectionBestFitness(Solution[] parents, Solution[] children) {
		Solution[] newGen = new Solution[parents.length];
		ArrayList<Solution> list = new ArrayList<Solution>();
		
		list.addAll(Arrays.asList(parents));
		list.addAll(Arrays.asList(children));

		//ordenar
		Collections.sort(list, new Comparator<Solution>() {
			public int compare(Solution o1, Solution o2) {
				if(o1.getFitness() > o2.getFitness()){
					return 1;
				}else if(o1.getFitness() < o2.getFitness()){
					return -1;
				}else{
					return 0;
				}
			}
			
		});
		
		//selecionar
		for (int i = 0; i < newGen.length; i++) {
			newGen[i] = list.get(i);
		}
		
		return newGen;
	}
	
	public static Solution[] DoSelectionTournament(Solution[] parents, Solution[] children) {
		Solution[] newGen = new Solution[parents.length];
		ArrayList<Solution> list = new ArrayList<Solution>();
		ArrayList<Solution> listNewGen = new ArrayList<Solution>();
		
		list.addAll(Arrays.asList(parents));
		list.addAll(Arrays.asList(children));
		
		// escolher randomicament N membros do torneio
		Random r = new Random();
		ArrayList<Solution> competitors = new ArrayList<Solution>();
		for (int i = 0; i < Parameters.numberTournament; i++) {
			competitors.add(list.get(r.nextInt(list.size()-1)));
		}
		
		int numVictories = 0;
		//efetuar comparacoes
		for (int i = 0; i < list.size(); i++) {
			Solution contestant = list.get(i);
			for (int j = 0; j < competitors.size(); j++) {
				if(contestant.getFitness() <= competitors.get(j).getFitness())
					numVictories++;
			}
			
			if(numVictories >= Parameters.numberTournament/2){
				if(listNewGen.size() < Parameters.numberParents){
					listNewGen.add(contestant);
				}
			}
			
			numVictories = 0;
		}
		
		//verificar se o numero de selecionados e' igual ao necessario (maximo)
		if(listNewGen.size() < Parameters.numberParents){
			for (int i = listNewGen.size(); i < Parameters.numberParents; i++) {
				listNewGen.add(list.get(r.nextInt(list.size()-1)));
			}
		}
		
		//transformar
		for (int i = 0; i < listNewGen.size(); i++) {
			newGen[i] = listNewGen.get(i);
		}
		
		return newGen;
	}
	
	// auxiliares
	
	private static double[] mixedVector(double[] v1, double[] v2, int pivot){
		double[] vec = new double[v1.length]; 
		
		for (int i = 0; i < vec.length; i++) {
			if(i < pivot){
				vec[i] = v1[i];
			}else{
				vec[i] = v2[i];
			}
		}
		
		return vec;
	}
	
	
}
 